import time
import matplotlib.pyplot as plt
import numpy as np
import cv2
import ctypes
from .camera import (
    AcquisitionMode,
    EmccdCameraByDll,
    ReadMode,
    ShutterMode,
    ShutterTTLSignal,
    Status,
    create_uint32_buffer,
    EmccdDll,
)
from .functions import (
    gaussian_2d,
    gaussian,
    calibrate_emccd_fit,
    emccd_fit_once,
    find_ions,
    counts_fit,
    find_ionsnew,
)
from os import getcwd, makedirs
from os.path import exists
from scipy.optimize import curve_fit
import threading
from skimage.feature import blob_log


class CameraDetection:
    def __init__(self, target_temperature=-80, path="./core/emccd/atmcd64d.dll"):
        self.dll_file_path = path
        self.target_temperature = target_temperature
        self.live_exposure_time = 0.1
        self.measure_exposure_time = 0.001
        self.camera = None  # Initialize camera object
        self.stop_threads = False
        self.current_image = None  # Initialize current_image attribute
        self.buffer = None
        self.buffer_data_mode = None
        self.zoom_window = None
        self.ion_position_list  = None
        self.mode = "Video"
        self.camera = EmccdCameraByDll(EmccdDll(self.dll_file_path))
        self.img_num = 1
        self.hbin = 1
        self.vbin = 1
        self.video_mode_init()
        #self.wait_for_temperature_stabilization()
        self.calibrate_parameter = None
        self.paused = False  #used for control whether pause the live camera display
        self.barkgroundimage=None
        

    def video_mode_init(self):
        """Initialize camera settings."""
        self.camera.set_temperature(self.target_temperature)
        self.camera.set_cooler(True)
        self.camera.set_shutter(ShutterTTLSignal.LOW, ShutterMode.PERMANENTLY_OPEN, 27, 27)

        if self.camera.support_baseline_clamp:
            self.camera.set_baseline_clamp(True)
        if self.camera.support_baseline_offset:
            self.camera.set_baseline_offset(0)

        self.camera.set_hs_speed(0, 0)
        self.camera.set_vs_speed(4)

        
        self.camera.set_pre_amp_gain(2)

        self.camera.set_em_advanced(True)
        self.camera.set_em_gain(1000)

        self.camera.set_read_mode(ReadMode.IMAGE)
        self.img_w, self.img_h = self.camera.get_detector_size()
        
        self.camera.set_image(hbin=1, vbin=1, hstart=0, hend=self.img_w, vstart=0, vend=self.img_h)
        self.img_size = self.img_w * self.img_h
        
        self.set_exposure_time(self.live_exposure_time)
        self.camera.dll.SetTriggerMode(0) #0 for internal trigger, 1 for external trigger
        self.set_acquisition_mode()

    def wait_for_temperature_stabilization(self):
        """Wait for the temperature to reach the target temperature."""
        while True:
            temperature = self.camera.get_temperature()
            if temperature > self.target_temperature:
                print(f"Still cooling ...  (t = {temperature} ℃)")
                time.sleep(1)
            else:
                print(f"Reached target temperature ...  (t = {temperature} ℃)")
                break

    def set_acquisition_mode(self, exposure_time=0.1, mode=AcquisitionMode.KINETICS):
        """Set acquisition mode and related settings."""
        self.camera.set_acquisition_mode(mode)
        self.camera.dll.SetNumberKinetics(self.img_num)
        self.camera.set_exposure_time(exposure_time)
        self.camera.dll.SetAccumulationCycleTime(0.1)
        self.camera.set_kinetic_cycle_time(0.1)
        self.camera.dll.SetTriggerMode(0)
        self.camera.set_em_gain(1000)

    def set_exposure_time(self, exposuretime):
        """Set the exposure time."""
        self.camera.set_exposure_time(exposuretime)

    def set_image(self, hbin=1, vbin=1, hstart=0, vstart=0):
        """Set the image area and binning."""
        self.camera.set_image(hbin=hbin, vbin=vbin, hstart=hstart, hend=self.img_w, vstart=vstart, vend=self.img_h)

    def set_img_num(self, img_num):
        """Set the number of images to capture."""
        self.img_num = img_num
        self.camera.dll.SetNumberKinetics(self.img_num)

    def capture_images(self, img_num=1):
        """Capture images and return them as a numpy array."""
        self.set_img_num(img_num)

        if self.buffer is None or ctypes.sizeof(self.buffer) < self.img_size * self.img_num * ctypes.sizeof(ctypes.c_uint32):
            self.buffer = create_uint32_buffer(self.img_size * self.img_num)

        self.camera.start_acquisition()

        while True:
            time.sleep(0.001)  # Decrease sleep time for more frequent status checks
            status = self.camera.get_status()
            if status == Status.IDLE:
                break

        self.camera.get_acquired_data(self.img_size * self.img_num, self.buffer)
        images = np.frombuffer(self.buffer, dtype=np.uint32).reshape((-1, self.img_h, self.img_w))
        return images[:img_num]
    
    def toggle_pause(self):
        """
        Toggle the paused state.
        """
        self.paused = not self.paused

    def display_live_images(self, window_name='Live Camera Feed', delay=1):
        """
        Display live images from the camera in a window.

        Args:
            window_name (str): The name of the window where the live feed is displayed. Defaults to 'Live Camera Feed'.
            delay (int): The delay in milliseconds between each frame update. Defaults to 1ms.
        """
        def run_in_background():
            self.video_mode_init()

            # Create a named window with the specified flags
            cv2.namedWindow(window_name, cv2.WINDOW_GUI_EXPANDED | cv2.WINDOW_KEEPRATIO | cv2.WINDOW_AUTOSIZE)
            # Set the window to always on top
            cv2.setWindowProperty(window_name, cv2.WND_PROP_TOPMOST, 1)

            # Initial mouse state
            self.drag_start = None
            self.dragging = False
            self.show_rectangle = False
            self.rectangle_end = None
            self.img_normalized = None

            def on_mouse(event, x, y, flags, param):
                if event == cv2.EVENT_LBUTTONDOWN:
                    self.drag_start = (x, y)
                    self.dragging = True
                    self.show_rectangle = True
                elif event == cv2.EVENT_MOUSEMOVE and self.dragging:
                    self.rectangle_end = (x, y)
                elif event == cv2.EVENT_LBUTTONUP:
                    self.dragging = False
                    if self.drag_start:
                        self.zoom_window = (min(self.drag_start[0], x), min(self.drag_start[1], y), abs(self.drag_start[0] - x), abs(self.drag_start[1] - y))
                        self.drag_start = None
                    else:
                        self.zoom_window = None
                    self.show_rectangle = False

            cv2.setMouseCallback(window_name, on_mouse)

            try:
                while True:
                    # Check if the paused flag is set
                    if not self.paused:
                        images = self.capture_images(img_num=1)
                        img = images[0]

                        # Convert the image to float32 format for normalization
                        img_float32 = img.astype(np.float32)

                        # Normalize the image and convert to 8-bit format for display
                        self.img_normalized = cv2.normalize(img_float32, None, 0, 255, cv2.NORM_MINMAX, cv2.CV_8U)

                        # Convert to RGB for color rectangle
                        img_rgb = cv2.cvtColor(self.img_normalized, cv2.COLOR_GRAY2BGR)

                        # If rectangle should be shown, draw it
                        if self.show_rectangle and self.drag_start and self.rectangle_end:
                            cv2.rectangle(img_rgb, self.drag_start, self.rectangle_end, (0, 0, 255), 1)

                        # If a zoom window is defined, zoom in
                        if self.zoom_window and self.zoom_window[2] > 0 and self.zoom_window[3] > 0:
                            roi = img_rgb[self.zoom_window[1]:self.zoom_window[1] + self.zoom_window[3], self.zoom_window[0]:self.zoom_window[0] + self.zoom_window[2]]
                            roi_resized = cv2.resize(roi, (img_rgb.shape[1], img_rgb.shape[0]))
                            cv2.imshow(window_name, roi_resized)
                        else:
                            cv2.imshow(window_name, img_rgb)

                    # Break the loop if the user presses 'q' or 'Q'
                    key = cv2.waitKey(delay) & 0xFF
                    if key == ord('q') or key == ord('Q'):
                        break
                    elif key == ord('p') or key == ord('P'):  # Added key check for pause
                        self.toggle_pause()

            finally:
                cv2.destroyAllWindows()
        # Start the background thread
        threading.Thread(target=run_in_background).start()


    def save_images(self, images):
        """Save images to the 'measure_image' folder."""
        folder = getcwd() + "\\measure_image\\"
        if not exists(folder):
            makedirs(folder)

        plt.clf()
        for i, image in enumerate(images):
            plt.figure()
            plt.imshow(image)
            plt.savefig(folder + f"measure{i + 1}.png")
            plt.clf()
    def fastcheckionnumber(self, img_num=3,debug=0):
        """Fast check the number of ions in the camera"""
        self.video_mode_init()
        images = self.capture_images(img_num=img_num)
        # if debug==1:
        #     print(len(images),img_num,np.shape(images))
        image_averaged = sum(images) / len(images)
        image_averagedmedian=cv2.medianBlur(np.float32(image_averaged), 5)
        ion_position_list =blob_log(image_averagedmedian, max_sigma=5 , threshold=1000)
        if debug==1:
            image_averaged_copy=np.copy(image_averagedmedian)
            array_min, array_max = image_averaged_copy.min(), image_averaged_copy.max()
            displayionimage = ((image_averaged_copy - array_min) / ( array_max - array_min) * 255).astype(np.uint8)
            blobs_points = ion_position_list[ion_position_list[:,1].argsort()]

            # Draw detected blobs as red circles on the original image
            for i, blob in enumerate(blobs_points):
                y, x, r = blob
                cv2.circle(displayionimage, (int(x), int(y)), int(np.sqrt(2)*r), (255, 0, 0), 1)  # Red circles with thickness 1
                cv2.putText(displayionimage, str(i + 1), (int(x) + int(np.sqrt(2)*r), int(y) - int(np.sqrt(2)*r)), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 0, 0),1)

            # Display the image with detected blobs using Matplotlib
            plt.figure(figsize=(10, 10))
            plt.imshow(displayionimage)
            plt.title(f'Detected ions: {len(blobs_points)}')
            plt.axis('off')
            plt.show()
            print(ion_position_list)
        return len(ion_position_list)
        
    def calibrate(self, img_num=100,debug=0,displayionindex=1):
        #this funciton find ion position,  while don't calibrate ion count suitablely use sequence.py calibrate_camera to find ion count
        """Calibrate the camera and perform EMCCD fitting on a series of images. Without the use of FPGA"""
        self.video_mode_init()
        images = self.capture_images(img_num=img_num)
        if debug==1:
            print(len(images),img_num,np.shape(images))
        image_averaged = sum(images) / len(images)
        
        if debug==0:
            ion_position_list=find_ionsnew(np.float32(image_averaged))
        else:
            ion_position_list = find_ions(np.float32(image_averaged))#,debug=debug
        
        if displayionindex==1:
            image_averaged_copy=np.copy(image_averaged)
            array_min, array_max = image_averaged_copy.min(), image_averaged_copy.max()
            displayionimage = ((image_averaged_copy - array_min) / ( array_max - array_min) * 255).astype(np.uint8)
            for ionindex, ion in enumerate(ion_position_list):
                y, x, r = ion[0],ion[1],ion[2]
                cv2.circle(displayionimage, (int(np.round(x, decimals=0)), int(np.round(y, decimals=0))), int(np.round(r, decimals=0)), (255, 0, 0), 1)  # Red circles with thickness 1
                cv2.putText(displayionimage, str(ionindex + 1), (int(np.round(x+r, decimals=0)), int(np.round(y-r, decimals=0))), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 0, 0),1)
            plt.figure(figsize=(10, 10))
            plt.imshow(displayionimage)
            plt.title(f'Detected ions: {len(ion_position_list)}')
            plt.axis('off')
            plt.show()
        assert len(ion_position_list) > 0, "No ion founded"
        self.ion_position_list = np.array(ion_position_list)
        self.calibrate_parameter = []
        self.images_seperated  = []
        return self.calibrate_images_fit(self.ion_position_list, image_averaged,debug=debug,backgroundsubstraction=0)
        #when find ion, because it doesn't use sequence, so there is no barkgroundimage.
        
    def calibrate_images_fit(self, ion_position_list, image_averaged,debug=0,backgroundsubstraction=1):
        fig, axes = plt.subplots(2, len(ion_position_list), figsize=(int(4*len(ion_position_list)), 6))
        # If there is only one row or column, make axes 2D for consistent indexing
        if len(axes.shape) == 1:
            axes = np.expand_dims(axes, axis=0 if axes.shape[0] == len(ion_position_list) else 1)

        self.images_seperated = []
        self.calibrate_parameter = []
        if backgroundsubstraction==1 and self.barkgroundimage is not None:
            image_averaged=image_averaged-self.barkgroundimage
        for i in range(len(ion_position_list)):
            ion_position = [ion_position_list[i, 0]/self.hbin, ion_position_list[i, 1]/self.vbin]
            ion_size = ion_position_list[i, 2]
            ion_image = image_averaged[
                int(np.round(ion_position[0] - 1.5*ion_size, decimals=0)): int(np.round(ion_position[0] + 1.5*ion_size, decimals=0))+1,
                int(np.round(ion_position[1] - 1.5*ion_size, decimals=0)): int(np.round(ion_position[1] + 1.5*ion_size, decimals=0))+1,
            ]
            if debug==1:
                print("before minus",i,np.median(ion_image), np.min(ion_image),ion_image)
            if not ( backgroundsubstraction==1 and self.barkgroundimage is not None ):
                ion_image = ion_image - np.min(np.sum(ion_image, axis=0))/np.shape(ion_image)[0]
            if debug==1:
                print("after minus",i,np.median(ion_image), np.min(ion_image),ion_image)
            self.images_seperated = self.images_seperated + [ion_image]
            # Plot in the upper row (0th row)
            axes[0][i].imshow(ion_image)
            axes[0][i].set_title(f'Ion Image {i+1}')
            axes[0][i].set_xlabel('Pixel')
            axes[0][i].set_ylabel('Pixel')
                        
            params_cali = self.calibrate_EMCCD_fit(ion_image,debug=debug)
            self.calibrate_parameter = self.calibrate_parameter + [params_cali]
            # Plot in the lower row (1st row)
            y = np.sum(ion_image, axis=0)
            x = range(len(y))
            axes[1][i].plot(x,y,'r',marker='.', linestyle='None', markersize=10)
            x = np.arange(0, len(y), len(y)/50)
            y = gaussian(x,params_cali["mu"],params_cali["sigma"],params_cali["amp"],params_cali["bias"])
            axes[1][i].plot(x,y)
            axes[1][i].set_xlabel('Pixel')
            axes[1][i].set_ylabel('Count')

        # To avoid overlapping of labels/titles with each other
        plt.tight_layout()
        plt.show()
        return self.calibrate_parameter

    def calibrate_EMCCD_fit(self, count_matrix,debug=0):
        """Perform calibration and fitting on EMCCD."""
        y = np.sum(count_matrix, axis=0)
        x = range(len(y))
        bounds = (np.array([0, 0, 0, -np.inf]), [np.inf, np.inf, np.inf, np.inf])
        if debug==1:
            print("the data x and y for curve_fit",x,y)
        params_cali, cov = curve_fit(
            gaussian, x, y, p0=[np.median(x), len(x) / 2, max(y), min(y)], bounds=bounds
        )
        return {
            "mu":    params_cali[0],
            "sigma": params_cali[1],
            "amp":   params_cali[2],
            "bias":  params_cali[3],
        }
    
    def data_mode_init(self, img_num=100, exposure_time=0.001, h_redundancy=10, v_redundancy=10, hbin=1, vbin=1, ext_trigger:int=1, output_flag=True):
        """
        In the data mode, camera only take the smallest region that cover all the ions' position. 
        This function first calculate that ROI (region of interest) based on self.ion_position_list.
        The ion_position_list consists of [[ion_1_x, ion_1_y, ion_1_size],...]
        Then set the ROI, the exposure duration, and change the EMCCD to be triggered by the external trigger.
        """
        self.mode = "Data"
        #calculate roi
        self.roi = self.roi_calculator(self.ion_position_list, h_redundancy, v_redundancy, hbin, vbin)
        self.hbin = hbin
        self.vbin = vbin
        self.img_h_data = int((self.roi["hend"] - self.roi["hstart"])/hbin)
        self.img_w_data = int((self.roi["vend"] - self.roi["vstart"])/vbin)
        #set camera roi
        self.camera.set_read_mode(ReadMode.IMAGE)
        self.camera.set_image(hbin=hbin, vbin=vbin, hstart=self.roi["hstart"], hend=self.roi["hend"], vstart=self.roi["vstart"], vend=self.roi["vend"])
        self.img_size_data_mode = self.roi["img_size"]
        #set acquisition mode
        self.img_num_data_mode = img_num
        self.camera.set_acquisition_mode(AcquisitionMode.KINETICS)
        self.camera.dll.SetNumberKinetics(self.img_num_data_mode)
        self.camera.set_exposure_time(exposure_time)
        self.camera.dll.SetAccumulationCycleTime(exposure_time)    #This function will set the accumulation cycle time to the nearest valid value not less than the given value.
        self.camera.set_kinetic_cycle_time(exposure_time)  #This function will set the kinetic cycle time to the nearest valid value not less than the given value.
        self.camera.dll.SetTriggerMode(ext_trigger) #0 for internal trigger, 1 for external trigger
        acquisition_time = self.camera.get_acquisition_times()
        if output_flag:
            print("Estimated Exposure Time: ", acquisition_time[0], "s") 
            print("Smallest Kinetic Cycle Time: ", acquisition_time[2], "s") 
            print("Estimated Total Time: ", acquisition_time[2]*img_num, "s") 
        
        if self.buffer_data_mode is None or ctypes.sizeof(self.buffer_data_mode) < self.img_size_data_mode * img_num * ctypes.sizeof(ctypes.c_uint32) or ctypes.sizeof(self.buffer_data_mode)% (self.img_size_data_mode * ctypes.sizeof(ctypes.c_uint32)) != 0 :
            self.buffer_data_mode = create_uint32_buffer(self.img_size_data_mode * self.img_num_data_mode)
        return acquisition_time[2] #Kinetic Cycle Time

    def roi_calculator(self, ion_position_list, h_redundancy, v_redundancy, hbin, vbin ):
        h_coordinates = [int(np.round(ion_position[1], decimals=0)) for ion_position in ion_position_list] # the camera's inside horizontal and vertical definition is different with the image we received.
        v_coordinates = [int(np.round(ion_position[0], decimals=0)) for ion_position in ion_position_list]
        hend     = max(h_coordinates) + h_redundancy
        hstart   = min(h_coordinates) - h_redundancy
        vend     = max(v_coordinates) + v_redundancy
        vstart   = min(v_coordinates) - v_redundancy
        hend    -= (hend-hstart)%hbin
        vend    -= (vend-vstart)%vbin
        img_size = int(np.round((hend-hstart)*(vend-vstart)/(hbin*vbin), decimals=0))
        self.ion_position_list_data_mode = self.ion_position_list - np.array([vstart,hstart,0])
        return {"hend":hend, "hstart":hstart, "vend":vend, "vstart":vstart, "img_size":img_size}

    def data_mode_capture_images(self):
        """Capture images and return them as a numpy array."""
        self.camera.start_acquisition()

        while True:
            time.sleep(0.001)  # Decrease sleep time for more frequent status checks
            status = self.camera.get_status()
            if status == Status.IDLE:
                break
        self.camera.get_acquired_data(self.img_size_data_mode * self.img_num_data_mode, self.buffer_data_mode)
        images = np.frombuffer(self.buffer_data_mode, dtype=np.uint32).reshape((-1, self.img_w_data, self.img_h_data))
        return images[:self.img_num_data_mode]
        
    def measure_and_fit(self):
        """Capture images, find ion positions, and perform EMCCD fitting."""
        images = self.data_mode_capture_images()
        counts = []
        for image in images:
            ion_position = [self.ion_position_list_data_mode[i, 0]/self.hbin,self.ion_position_list_data_mode[i, 1]/self.vbin]
            ion_size = self.ion_position_list_data_mode[0, 2]
            ion_image = image[
                    int(np.round(ion_position[0] - 1.5*ion_size, decimals=0)): int(np.round(ion_position[0] + 1.5*ion_size, decimals=0))+1,
                    int(np.round(ion_position[1] - 1.5*ion_size, decimals=0)): int(np.round(ion_position[1] + 1.5*ion_size, decimals=0))+1,
            ]
            ion_image = ion_image - np.median(ion_image)
            count = self.EMCCD_fit_once(ion_image, self.calibrate_parameter[0])
            counts = counts + [count]
        return counts
    
    def read(self):
        """Capture images, find ion positions, and perform EMCCD fitting."""
        self.camera.get_acquired_data(self.img_size_data_mode * self.img_num_data_mode, self.buffer_data_mode)
        images = np.frombuffer(self.buffer_data_mode, dtype=np.uint32).reshape((-1, self.img_w_data, self.img_h_data))
        images = images[:self.img_num_data_mode]
        return images
#     #还要做fit
    def read_and_fit(self,debug=0,backgroundsubstraction=1,threshold=None):
        """Capture images, find ion positions, and perform EMCCD fitting."""
        self.camera.get_acquired_data(self.img_size_data_mode * self.img_num_data_mode, self.buffer_data_mode)
        images = np.frombuffer(self.buffer_data_mode, dtype=np.uint32).reshape((-1, self.img_w_data, self.img_h_data))
        images = images[:self.img_num_data_mode]
        counts = []
        errorimageindex=[]
        for image_index,image in enumerate(images):
            # if debug==1:
            #     print("this is raw data before background substraction")
            #     plt.figure(figsize=(8, 6))
            #     img = plt.imshow(image, cmap='viridis')  # You can choose other colormaps as well
            #     # Add a colorbar to the plot
            #     plt.colorbar(img, label='Color Scale')  # The label adds a description to the colorbar
            #     plt.show()
            if backgroundsubstraction==1 and self.barkgroundimage is not None:
                image=image-self.barkgroundimage
            # if debug==1:
            #     print("this is  data after background substraction")
            #     plt.figure(figsize=(8, 6)) 
            #     img = plt.imshow(image, cmap='viridis')  # You can choose other colormaps as well
            #     # Add a colorbar to the plot
            #     plt.colorbar(img, label='Color Scale')  # The label adds a description to the colorbar
            #     plt.show()
            for i in range(len(self.ion_position_list_data_mode)):
                #读取后离子的信息，比如离子的位置、大小
                ion_position = [self.ion_position_list_data_mode[i, 0]/self.hbin,self.ion_position_list_data_mode[i, 1]/self.vbin]
                ion_size = self.ion_position_list_data_mode[i, 2]
                ion_image = image[
                    int(np.round(ion_position[0] - 1.5*ion_size, decimals=0)): int(np.round(ion_position[0] + 1.5*ion_size, decimals=0))+1,
                    int(np.round(ion_position[1] - 1.5*ion_size, decimals=0)): int(np.round(ion_position[1] + 1.5*ion_size, decimals=0))+1,
                ]
                if not ( backgroundsubstraction==1 and self.barkgroundimage is not None ):
                    ion_image = ion_image - np.min(np.sum(ion_image, axis=0))/np.shape(ion_image)[0]
                # if debug==1:
                #     print("ion index, position, size",i,ion_position,ion_size)
                #     plt.figure(figsize=(8, 6))
                #     plt.imshow(ion_image)
                    # print(ion_image)
                # ion_position = [int(self.ion_position_list_data_mode[i, 0]/self.hbin), int(self.ion_position_list_data_mode[i, 1]/self.vbin)]
                # ion_size = self.ion_position_list_data_mode[i, 2]
                #print("ith ion",i)
                #根据校准后离子的信息，比如离子的位置、大小。确定数据读取的区域
                # ion_image = image[
                #     int(ion_position[0] - 2*ion_size) - 1 : int(ion_position[0] + 2*ion_size) + 1,
                #     int(ion_position[1] - 2*ion_size) - 1 : int(ion_position[1] + 2*ion_size) + 1,
                # ]
                # ion_image = ion_image - np.median(ion_image)
                
                #print("ion_image",ion_image)
                ###########TEMP###########
                #print(ion_image)
                ###########TEMP END###########
                #对数据进行fit。
                count = self.EMCCD_fit_once(ion_image, self.calibrate_parameter[i])
                if debug==1 and threshold is not None and count<= threshold[i]:
                    self.EMCCD_fit_forerrorimage(ion_image, self.calibrate_parameter[i])
                    errorimageindex.append(image_index)
                    print("this is not bright ion image of bright ion where the ion index, position, size")
                    plt.figure(figsize=(8, 6))
                    plt.imshow(ion_image, cmap='viridis', vmin=0, vmax=200)
                    plt.colorbar()
                counts = counts + [count]
                #print("count",count)
        if debug==1 and threshold is not None:
            errorimageindex=np.unique(errorimageindex)
            print("this is total image with not bright ion image of bright ion before background substraction")
            for imageindex in errorimageindex:
                plt.figure(figsize=(8, 6))
                plt.imshow(images[imageindex], cmap='viridis', vmin=500, vmax=700)
                plt.colorbar()
        return counts

# # 直接相加
#     def read_and_fit(self):
#             """Capture images, find ion positions, and perform EMCCD fitting."""
#             self.camera.get_acquired_data(self.img_size_data_mode * self.img_num_data_mode, self.buffer_data_mode)
#             images = np.frombuffer(self.buffer_data_mode, dtype=np.uint32).reshape((-1, self.img_w_data, self.img_h_data))
#             images = images[:self.img_num_data_mode]
#             counts = []
#             for image in images:
#                 for i in range(len(self.ion_position_list_data_mode)):
#                     #读取后离子的信息，比如离子的位置、大小
#                     ion_position = [int(self.ion_position_list_data_mode[i, 0]/self.hbin), int(self.ion_position_list_data_mode[i, 1]/self.vbin)]
#                     ion_size = self.ion_position_list_data_mode[i, 2]
#                     #print("ith ion",i)
#                     #根据校准后离子的信息，比如离子的位置、大小。确定数据读取的区域
#                     ion_image = image[
#                         int(ion_position[0] - 2*ion_size) - 1 : int(ion_position[0] + 2*ion_size) + 1,
#                         int(ion_position[1] - 2*ion_size) - 1 : int(ion_position[1] + 2*ion_size) + 1,
#                     ]
#                     ion_image = ion_image - np.median(ion_image)

#                     #print("ion_image",ion_image)
#                     ###########TEMP###########
#                     #print(ion_image)
#                     ###########TEMP END###########
#                     #对数据进行fit。
#                     # count = self.EMCCD_fit_once(ion_image, self.calibrate_parameter[i])
#                     count=np.sum(ion_image)
#                     counts = counts + [count]
#                     #print("count",count)
#             return counts


    
    def EMCCD_fit_once(self, count_matrix, params_cali):
        """Perform calibration and fitting on EMCCD."""
        y = np.sum(count_matrix, axis=0)
        x = range(len(y))
        # print("parama before fit",params_cali)
        bounds = (np.array([params_cali['mu'], params_cali['sigma'], 0, params_cali['bias']])-1e-5, [params_cali['mu'], params_cali['sigma'], np.inf, params_cali['bias']])
        params_cali, cov = curve_fit(
            gaussian, x, y, bounds=bounds
        )
        # print("parama after fit",params_cali)
        # print("this is the fit result")
        # plt.figure(figsize=(8, 6))
        # plt.plot(x,y,'r',marker='.', linestyle='None', markersize=10)
        # x = np.arange(0, len(y), len(y)/50)
        # y = gaussian(x,params_cali[0],params_cali[1],params_cali[2],params_cali[3])
        # plt.plot(x,y)
        return params_cali[2]
    def EMCCD_fit_forerrorimage(self, count_matrix, params_cali):
        """Perform calibration and fitting on EMCCD."""
        y = np.sum(count_matrix, axis=0)
        x = range(len(y))
        # print("parama before fit",params_cali)
        bounds = (np.array([params_cali['mu'], params_cali['sigma'], 0, params_cali['bias']])-1e-5, [params_cali['mu'], params_cali['sigma'], np.inf, params_cali['bias']])
        params_cali, cov = curve_fit(
            gaussian, x, y, bounds=bounds
        )
        # print("parama after fit",params_cali)
        print("this is the fit result of those failed image")
        plt.figure(figsize=(8, 6))
        plt.plot(x,y,'r',marker='.', linestyle='None', markersize=10)
        x = np.arange(0, len(y), len(y)/50)
        y = gaussian(x,params_cali[0],params_cali[1],params_cali[2],params_cali[3])
        plt.plot(x,y)
        return params_cali[2]
    def plot_and_save_images(self, images, folder):
        """Plot and save images to the specified folder."""
        plt.clf()
        for i, image in enumerate(images, start=1):
            plt.figure()
            plt.imshow(image)
            plt.savefig(folder + f"measure{i}.png")
            plt.clf()

    def get_amp_list(self, images, ion_position, ion_size, params_cali):
        """Get amplitude list from images and given parameters."""
        amp_list = []
        for image in images:
            images_single_shot = image[
                ion_position[0] - ion_size : ion_position[0] + ion_size,
                ion_position[1] - ion_size : ion_position[1] + ion_size,
            ]
            images_single_shot = images_single_shot - np.median(images_single_shot)
            x = range(len(images_single_shot))
            amp_list.append(emccd_fit_once(x, images_single_shot, params_cali))

        return amp_list

    def plot_histograms(self, amp_list_dark, amp_list):
        """Plot histograms of dark and regular amplitude lists."""
        plt.hist(amp_list_dark)
        plt.clf()
        plt.hist(amp_list_dark, 20)
        plt.hist(amp_list, 40)
        plt.clf()





